Skip to content

transform: optimize string / byte conversions#5492

Draft
jakebailey wants to merge 6 commits into
tinygo-org:devfrom
jakebailey:fix-4045
Draft

transform: optimize string / byte conversions#5492
jakebailey wants to merge 6 commits into
tinygo-org:devfrom
jakebailey:fix-4045

Conversation

@jakebailey

@jakebailey jakebailey commented Jul 3, 2026

Copy link
Copy Markdown
Member

The commits are clearer about this, and you can see the output changes as the tests are split apart.

Fixes #4045

  • bytes.Equal no longer allocates.

@aykevl aykevl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of code to review. And also, it doesn't include a lot of comments to explain why these transforms are safe and what they're even doing in the first place (although the names do help - sometimes, see below). Can you maybe look through the code to make it a bit more compact/readable?

Comment thread transform/rtcalls.go Outdated
return
}

optimizeStringFromBytesStringEqual(mod, stringFromBytes)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you put these in separate functions, instead of just writing the contents here?

Comment thread transform/rtcalls.go Outdated
return false
}

func isSafeBetweenStringFromBytesUse(inst, stringFromBytes llvm.Value, allowedCalls map[llvm.Value]struct{}) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example: isSafeBetweenStringFromBytesUse ... what does that even mean? I would suggest renaming or at least explaining a bit more what it does in a comment.

Comment thread transform/rtcalls.go Outdated
Comment on lines +169 to +172
func optimizeStringFromBytesMapKey(mod llvm.Module, stringFromBytes llvm.Value) {
optimizeStringFromBytesMapKeyUses(mod.NamedFunction("runtime.hashmapStringGet"), stringFromBytes)
optimizeStringFromBytesMapKeyUses(mod.NamedFunction("runtime.hashmapStringDelete"), stringFromBytes)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not put it in a loop instead of a new function? I think a loop would make this more readable since it keeps all the relevant code together.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funnily it was all together and then I split it apart... I'll redo it

@jakebailey
jakebailey marked this pull request as draft July 4, 2026 17:13
@jakebailey

Copy link
Copy Markdown
Member Author

I meant to send this as a draft since I don't have a lot of free time and wasn't done 😅

@aykevl

aykevl commented Jul 4, 2026

Copy link
Copy Markdown
Member

If I can make a recommendation: usually these transforms are structured as follows:

  1. Find uses (like stringFromBytes)
  2. Check each use if it's a safe use
  3. Replace the use with the original string
  4. If all uses were converted, remove the original stringFromBytes

That means having just a single optimization pass, otherwise if a stringFromBytes result is used in two different ways it wouldn't get optimized even if it is safe to do so.

Note that "safe uses" also means checking that every instruction between the stringFromBytes call and the use does not touch the backing slice. Including across branches, if you want to do that (that is probably not necessary for cases like bytes.Equal which presumably is just a single basic block). An integer binary op will be fine, a load/store may be fine if you can prove it doesn't touch the same underlying slice (this is hard!), a function call is most likely not safe unless it's part of a pre-approved list of function calls (such as using the string as a map key).

@jakebailey
jakebailey force-pushed the fix-4045 branch 2 times, most recently from 5db2361 to ed91ad3 Compare July 22, 2026 15:30
Rewrite string equality comparisons when either operand comes from a
temporary []byte-to-string conversion to compare the original slice
data directly.

Each comparison is optimized independently only when it is in the same
basic block and every intervening instruction is known not to mutate
the slice. The copied string remains for any other uses.

Fixes issue 4045
Reuse the temporary []byte-to-string comparison rewrite for
runtime.stringLess. The same-block safety check preserves conversions
when intervening instructions could mutate the source slice.
Rewrite len(string(b)) to use the original slice length. This is always
valid even when the copied string must remain for other uses.
@jakebailey

Copy link
Copy Markdown
Member Author

Rewrote this and dropped out all of the followups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bytes.Equal shouldn't allocate

2 participants